package com.jwater.core.utils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jwater.core.common.S;

import jwater.com.jcraft.jsch.ChannelExec;
import jwater.com.jcraft.jsch.ChannelSftp;
import jwater.com.jcraft.jsch.ChannelSftp.LsEntry;
import jwater.com.jcraft.jsch.JSch;
import jwater.com.jcraft.jsch.JSchException;
import jwater.com.jcraft.jsch.Session;
import jwater.com.jcraft.jsch.SftpATTRS;
import jwater.com.jcraft.jsch.SftpException;
import jwater.org.apache.commons.io.IOUtils;

/**
 * @description:
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
public class JSchUtils {
    protected static Logger logger = LoggerFactory.getLogger(JSchUtils.class);

    protected static int timeout = 60000;
    protected static int channelTimeout = 30000;


    /**
     * 创建session
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @return
     * @throws JSchException
     */
    protected static MySession createSession(String ip, int port, String username, String password)
            throws JSchException {
        JSch jsch = new JSch();
        Session session = jsch.getSession(username, ip, port);
        session.setPassword(password);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setConfig("userauth.gssapi-with-mic", "no");
        session.connect(timeout);

        MySession mySession = new MySession();
        mySession.session = session;
        mySession.isConnect = true;
        return mySession;
    }

    /**
     * 关闭session
     *
     * @param mySession
     */
    public static void closeSession(MySession mySession) {
        if (mySession == null) {
            return;
        }
        mySession.session.disconnect();
    }

    /**
     * 执行命令
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param cmd
     * @return
     */
    public static ExeResult exec(String ip, int port, String username, String password, String cmd) {
        logger.info("exec:" + cmd);
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelExec channel = null;
        ByteArrayOutputStream out = null;
        ByteArrayOutputStream err = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelExec) session.session.openChannel("exec");
            out = new MyByteArrayOutputStream(false, "info");
            channel.setOutputStream(out);
            err = new MyByteArrayOutputStream(true, "error");
            channel.setErrStream(err);
            channel.setCommand(cmd);
            channel.connect(channelTimeout);
            while (!channel.isClosed()) {
                Thread.sleep(200);
            }
            result.exitStatus = channel.getExitStatus();
            result.outStr = new String(out.toByteArray(), S.charset);
            result.outErr = new String(err.toByteArray(), S.charset);
        } catch (Throwable e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.warn("exec......" + e.toString());
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(err);
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return result;
    }

    /**
     * 上传文件
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param src
     * @param dst
     * @return
     */
    public static ExeResult putFile(String ip, int port, String username, String password, InputStream src,
                                    String dst) {
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelSftp channel = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelSftp) session.session.openChannel("sftp");
            channel.connect(channelTimeout);
            dst.replace("\\", "/");
            String[] items = dst.split("/");
            String lastItem = "";
            for (int i = 1; i < items.length - 1; i++) {
                lastItem = lastItem + "/" + items[i];
                try {
                    channel.stat(lastItem);
                } catch (SftpException sftpException) {
                    channel.mkdir(lastItem);
                }
            }
            channel.put(src, dst);
            result.exitStatus = channel.getExitStatus();
        } catch (Exception e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.error(e.toString(), e);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return result;
    }

    /**
     * 移除文件
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param dst
     * @return
     */
    public static ExeResult removeFile(String ip, int port, String username, String password, String dst) {
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelSftp channel = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelSftp) session.session.openChannel("sftp");
            channel.connect(channelTimeout);
            dst.replace("\\", "/");
            channel.rm(dst);
            result.exitStatus = channel.getExitStatus();
        } catch (Exception e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.error(e.toString(), e);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return result;
    }

    /**
     * 创建目录
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param dst
     * @return
     */
    public static ExeResult mkdir(String ip, int port, String username, String password, String dst) {
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelSftp channel = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelSftp) session.session.openChannel("sftp");
            channel.connect(channelTimeout);
            try {
                channel.mkdir(dst);
            } catch (SftpException sftpException) {
                logger.warn(sftpException.toString());
            }
            result.exitStatus = channel.getExitStatus();
        } catch (Exception e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.error(e.toString(), e);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return result;
    }

    /**
     * 罗列文件
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param dstDir
     * @param recursive
     * @return
     */
    public static List<String> listFiles(String ip, int port, String username, String password, String dstDir,
                                         boolean recursive) {
        List<String> list = new ArrayList<String>();
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelSftp channel = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelSftp) session.session.openChannel("sftp");
            channel.connect(channelTimeout);
            list(list, channel, dstDir, recursive);
            result.exitStatus = channel.getExitStatus();
        } catch (Exception e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.error(e.toString(), e);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return list;
    }

    /**
     * 递归罗列文件
     *
     * @param list
     * @param channel
     * @param dstDir
     * @param recursive
     */
    @SuppressWarnings("rawtypes")
    protected static void list(List<String> list, ChannelSftp channel, String dstDir, boolean recursive) {
        try {
            Vector vector = channel.ls(dstDir);
            Iterator it = vector.iterator();
            while (it.hasNext()) {
                LsEntry lsEntry = (LsEntry) it.next();
                String filename = lsEntry.getFilename();
                if (!".".equals(filename) && !"..".equals(filename)) {
                    SftpATTRS stat = channel.stat(dstDir + "/" + filename);
                    if (stat.isDir()) {
                        if (recursive) {
                            list(list, channel, dstDir + "/" + filename, recursive);
                        }
                    } else {
                        list.add(dstDir + "/" + filename);
                    }
                }
            }
        } catch (SftpException sftpException) {
            logger.warn(sftpException.toString());
        }
    }

    /**
     * 获取文件
     *
     * @param ip
     * @param port
     * @param username
     * @param password
     * @param dst
     * @return
     */
    public static byte[] getFile(String ip, int port, String username, String password, String dst) {
        ExeResult result = new ExeResult();
        MySession session = null;
        ChannelSftp channel = null;
        try {
            session = createSession(ip, port, username, password);
            result.connectSuccess = true;
            channel = (ChannelSftp) session.session.openChannel("sftp");
            channel.connect(channelTimeout);
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                channel.get(dst, baos);
                baos.close();
                return baos.toByteArray();
            } catch (SftpException sftpException) {
                logger.warn(sftpException.toString());
            }
            result.exitStatus = channel.getExitStatus();
        } catch (Exception e) {
            if (session != null) {
                session.isConnect = false;
            }
            logger.error(e.toString(), e);
        } finally {
            if (channel != null) {
                channel.disconnect();
            }
            closeSession(session);
        }
        return new byte[0];
    }

    protected static class MySession {
        protected Session session;
        protected boolean isConnect = false;
    }

    /**
     * 重写ByteArrayOutputStream
     */
    protected static class MyByteArrayOutputStream extends ByteArrayOutputStream {
        protected boolean printLog = false;
        protected String logLevel = "debug";

        public MyByteArrayOutputStream(boolean printLog, String logLevel) {
            this.printLog = printLog;
            this.logLevel = logLevel;
        }

        @Override
        public synchronized void write(byte[] b, int off, int len) {
            super.write(b, off, len);
            try {
                if (printLog) {
                    if ("error".equals(logLevel)) {
                        logger.error(new String(b, off, len - 1, S.charset));
                    } else {
                        logger.info(new String(b, off, len - 1, S.charset));
                    }
                }
            } catch (Exception e) {
                logger.error(e.toString(), e);
            }
        }
    }

    /**
     * 执行结果
     */
    public static class ExeResult {
        public int exitStatus;
        public String outStr;
        public String outErr;
        public boolean connectSuccess = false;

        @Override
        public String toString() {
            return "ExeResult [exitStatus=" + exitStatus + ", outStr=" + outStr + ", outErr=" + outErr
                    + ", connectSuccess=" + connectSuccess + "]";
        }
    }
}
